/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 *
 */

import * as util from 'util';

import { AdaptorError, AdaptorErrorMessage, ErrorUtil, HvigorErrorAdaptor } from '@ohos/hvigor-logger';
import type { Configuration, Level, Logger } from 'log4js';
import * as log4js from 'log4js';

import { hvigorTrace } from '../common/trace/hvigor-trace.js';
import { MetricLogType } from '../metrics/event/log-event.js';
import { MetricFactory } from '../metrics/metric-factory.js';
import { FileLogger } from './adaptor/fileLogger.js';
import { getConfiguration, getLevel, setCategoriesLevel, setConfiguration, updateConfiguration } from './default-configuration.js';

/**
 * 基于log4js封装的HvigorLogger
 *
 * @since 2022/03/02
 */
export class HvigorLogger {
  private static instanceMap = new Map<string, unknown>();

  protected _logger: Logger;

  protected readonly _filelogger: Logger;

  protected readonly anonymizeFileLogger: FileLogger;

  protected durationId: string | undefined;

  protected constructor(category?: string) {
    // 理论上说，只要configure一次就可以了，不需要在构造函数里面每次都configure
    // 但由于其他的自定义插件可能也会require log4js、然后执行configure，顶掉我们的configuration
    // 所以暂时保留每次configure的动作
    log4js.configure(updateConfiguration());
    this._logger = log4js.getLogger(category);
    this._logger.level = getLevel();

    // filelogger是生成持久化文件的日志记录类，与logger类解耦进行单独配置权限等级
    this._filelogger = log4js.getLogger('debug-file');

    // 需要匿名化的日志
    this.anonymizeFileLogger = new FileLogger(log4js.getLogger('debug-file'));
  }

  protected static getInstance<T>(logger: any, category?: string): T {
    const name = `${logger.name}:${category}`;
    if (!this.instanceMap.has(name)) {
      this.instanceMap.set(name, new logger(category));
    }
    return this.instanceMap.get(name)! as T;
  }

  /**
   * 获取对于类别的HvigorLogger实例
   *
   * @param {string} category 默认是default
   * @return {HvigorLogger}
   */
  public static getLogger(category?: string): HvigorLogger {
    return this.getInstance(HvigorLogger, category);
  }

  public static getLoggerWithDurationId(category: string, durationId: string): HvigorLogger {
    const logger = { ...(this.getInstance(HvigorLogger, category) as HvigorLogger) };
    const hvigorLogger: HvigorLogger = Object.setPrototypeOf(logger, HvigorLogger.prototype);
    hvigorLogger.durationId = durationId;
    return hvigorLogger;
  }

  public static clean() {
    HvigorLogger.instanceMap.clear();
  }

  log(level: Level | string, ...args: unknown[]): void {
    this.createLogEventByDurationId(level, MetricLogType.INFO, ...args);
    this._logger.log(level, ...args);
    this._filelogger.log(level, ...args);
  }

  debug(message: unknown, ...args: unknown[]): void {
    this.createLogEventByDurationId(message, MetricLogType.DEBUG, ...args);
    this._logger.debug(message, ...args);
    this._filelogger.debug(message, ...args);
  }

  info(message: unknown, ...args: unknown[]): void {
    this.createLogEventByDurationId(message, MetricLogType.INFO, ...args);
    this._logger.info(message, ...args);
    this._filelogger.info(message, ...args);
  }

  warn(message: unknown, ...args: unknown[]): void {
    if (message === undefined || message === '') {
      return;
    }
    this.createLogEventByDurationId(message, MetricLogType.WARN, ...args);
    this._logger.warn(message, ...args);
    this._filelogger.warn(message, ...args);
  }

  error(message: unknown, ...args: unknown[]): void {
    this.createLogEventByDurationId(message, MetricLogType.ERROR, ...args);
    this._logger.error(message, ...args);
    this._filelogger.error(message, ...args);
  }

  anonymizeDebug(message: unknown, ...args: unknown[]): void {
    this._logger.debug(message, ...args);
    const [anonymizeMsg, ...anonymizeArgs] = this.anonymizeFileLogger.debug(message, ...args);
    this.createLogEventByDurationId(anonymizeMsg, MetricLogType.DEBUG, ...anonymizeArgs);
  }

  _printTaskExecuteInfo(taskPath: string, time: string) {
    this._logger.info(`Finished :${taskPath}... after ${time}`);
    this._filelogger.info(`Finished :${taskPath}... after ${time}`);
  }

  _printFailedTaskInfo(taskPath: string) {
    this._logger.error(`Failed :${taskPath}... `);
    this._filelogger.error(`Failed :${taskPath}... `);
  }

  _printDisabledTaskInfo(taskPath: string) {
    this._logger.info(`Disabled :${taskPath}... `);
    this._filelogger.info(`Disabled :${taskPath}... `);
  }

  _printUpToDateTaskInfo(taskPath: string) {
    this._logger.info(`UP-TO-DATE :${taskPath}...  `);
    this._filelogger.info(`UP-TO-DATE :${taskPath}...  `);
  }

  _printStackErrorToFile(message: unknown, ...args: unknown[]): void {
    this._filelogger.error(message, ...args);
  }

  errorMessageExit(message: string, ...args: unknown[]): void {
    throw new Error(util.format(message, ...args));
  }

  errorExit(e: Error, message?: string, ...args: unknown[]): void {
    if (message) {
      MetricFactory.createLogEvent(this.getMessage(message, ...args), MetricLogType.ERROR);
      this._logger.error(message, args);
      this._filelogger.error(message, args);
    }
    this._logger.error(e.stack);
    this._filelogger.error(e.stack);
    if (!e.stack) {
      return;
    }
    MetricFactory.createLogEvent(e.stack, MetricLogType.ERROR);
    throw e;
  }

  getLevel(): Level | string {
    return this._logger.level;
  }

  setLevel(level: Level | string) {
    this._logger.level = level;
  }

  createLogEventByDurationId(message: unknown, logType: MetricLogType, ...args: unknown[]) {
    if (typeof message === 'string') {
      const logEvent = MetricFactory.createLogEvent(this.getMessage(message, ...args), logType);
      if (this.durationId) {
        logEvent.setDurationId(this.durationId);
      }
    }
    return message;
  }

  getMessage(message: string, ...args: unknown[]) {
    if (args.length > 0) {
      return util.format(message, ...args);
    }
    return message;
  }

  protected getAdaptor(errorId: string) {
    return new HvigorErrorAdaptor(errorId);
  }

  /**
   * 将适配器返回的里的错误信息转成最终打印的错误信息
   * @param {AdaptorErrorMessage} adaptorErrorMessage
   * @returns {string}
   * @protected
   */
  protected combinePhase(adaptorErrorMessage: AdaptorErrorMessage) {
    hvigorTrace.traceErrorMessage(adaptorErrorMessage);
    // 这里理论上还要加个adaptorErrorMessage.code，但是不适配ark-compiler-toolchain.json、restool.json里的报错还没有code
    if (adaptorErrorMessage.solutions) {
      return ErrorUtil.combinePhase({
        code: adaptorErrorMessage.code!,
        cause: adaptorErrorMessage.message,
        position: '',
        solutions: adaptorErrorMessage.solutions,
        moreInfo: adaptorErrorMessage.moreInfo,
      });
    }
    return adaptorErrorMessage.message;
  }

  protected formatErrorAdaptor(errorId: string, messages?: unknown[], solutions?: unknown[][]) {
    let errorAdaptor = this.getAdaptor(errorId);
    if (messages) {
      errorAdaptor = errorAdaptor.formatMessage(...messages);
    }
    if (solutions) {
      solutions.forEach((args, index) => {
        errorAdaptor = errorAdaptor.formatSolutions(index, ...args);
      });
    }

    return errorAdaptor;
  }

  /**
   * 三段式打印错误信息(不阻塞构建)
   * @param adaptorErrorMessages AdaptorErrorMessage的数组
   */
  printErrorWithAdaptorErrorMessage(adaptorErrorMessages: AdaptorErrorMessage[]) {
    const errorMessage = this.combinePhase(adaptorErrorMessages[0]);
    this._logger.error(errorMessage);
    for (let i = 1; i < adaptorErrorMessages.length; ++i) {
      this.combinePhase(adaptorErrorMessages[i]);
    }
  }

  /**
   * 三段式打印错误信息(不阻塞构建)
   * @param errorId 错误码表文件(如：hvigor.json)中的key
   * @param messages message格式化字符串
   * @param solutions solutions格式化字符串，二维数组，每一行对应solution中的一行
   */
  printError(errorId: string, messages?: unknown[], solutions?: unknown[][]) {
    const errorAdaptor = this.formatErrorAdaptor(errorId, messages, solutions);
    this.printErrorWithAdaptorErrorMessage(errorAdaptor.getErrorMessage());
  }

  /**
   * 三段式打印错误信息(阻塞构建)
   * @param errorId 错误码表文件(如：hvigor.json)中的key
   * @param messages message格式化字符串
   * @param solutions solutions格式化字符串，二维数组，每一行对应solution中的一行
   * @param stack stack最终打印的堆栈
   */
  printErrorExit(errorId: string, messages?: unknown[], solutions?: unknown[][], stack?: string) {
    const errorAdaptor = this.formatErrorAdaptor(errorId, messages, solutions);
    const errorMessage = this.combinePhase(ErrorUtil.getFirstErrorAdaptorMessage(errorAdaptor.getErrorMessage()));
    throw new AdaptorError(errorMessage, stack);
  }

  /**
   * 在 hvigorfile.ts 中调用的函数如果使用 printErrorExit 报错退出会打印 throw 那一行的代码，而压缩后的代码只有一行，会把整个文件的代码都打印出来
   * 所以新增此方法避免打印多余信息影响阅读
   */
  printErrorExitWithoutStack(errorId: string, messages?: unknown[], solutions?: unknown[][]) {
    this.printError(errorId, messages, solutions);
    process.exit(-1);
  }
}

export function evaluateLogLevel(level: Level, ignoreLevelCategoryFilterArr?: string[]): void {
  setCategoriesLevel(level, ignoreLevelCategoryFilterArr);
  log4js.shutdown();
  log4js.configure(updateConfiguration());
}

/**
 * 这个方法会对configuration里加入daemon或者daemon-client的部分
 * 之后调用setConfiguration时，就会使用包含daemon/daemon-client的部分
 *
 * @param {Configuration} configuration
 */
export function configure(configuration: Configuration): void {
  const oldConfig: Configuration = getConfiguration();
  const result: Configuration = {
    appenders: {
      ...oldConfig.appenders,
      ...configuration.appenders,
    },
    categories: {
      ...oldConfig.categories,
      ...configuration.categories,
    },
  };
  setConfiguration(result);
  log4js.shutdown();
  log4js.configure(result);
}
